package pers.lxl.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import pers.lxl.netty.conf.NettyConfig;

import java.util.Date;

/**
 * @version v1.0
 * @description: 接收/处理/响应请求的核心类
 * @author: lxl
 * @date: 2018/9/12 21:07
 */
public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> {

    private WebSocketServerHandshaker handshaker;

    private static final String WEB_SOCKET_URL = "ws://localhost:8888/webSocket";

    /**
     * 服务端处理客户端websocket请求的核心方法
     *
     * @param context 上下文对象
     * @param msg
     * @throws Exception
     */
    @Override
    protected void messageReceived(ChannelHandlerContext context, Object msg) throws Exception {

        // 处理客户端发起的http握手请求
        if (msg instanceof FullHttpRequest) {
            handHttpRequest(context, (FullHttpRequest) msg);

            // 处理客户端发送的webSocket请求
        } else if (msg instanceof WebSocketFrame) {
            handWebSocketFrame(context, (WebSocketFrame) msg);
        }
    }

    /**
     * 工程出现异常时调用这个方法
     *
     * @param ctx   上下文对象
     * @param cause 异常
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        // 关闭通道
        ctx.close();
    }

    /**
     * 客户端与服务端创建连接的时候调用这个方法
     *
     * @param ctx 上下文对象
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        NettyConfig.group.add(ctx.channel());
        System.out.println("客户端与服务端连接开启");
    }

    /**
     * 客户端与服务端断开连接的时候调用这个方法
     *
     * @param ctx 上下文对象
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        NettyConfig.group.remove(ctx.channel());
        System.out.println("客户端与服务端连接关闭！");
    }

    /**
     * 服务端接收客户端发送过来的数据结束后调用这个方法
     *
     * @param ctx 上下文对象
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }


    /**
     * 处理http请求
     *
     * @param context 通道上下文对象
     * @param request http请求对象
     */
    private void handHttpRequest(ChannelHandlerContext context, FullHttpRequest request) {
        // 如果请求不成功或者不是webSocket请求
        if (!request.getDecoderResult().isSuccess() || !("websocket".equals(request.headers().get("Upgrade")))) {
            sendHttpResponse(context, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.BAD_REQUEST));
            return;
        }

        WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(WEB_SOCKET_URL, null, false);
        handshaker = factory.newHandshaker(request);
        if (handshaker == null) {
            // 版本不支持webSocket
            WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(context.channel());
        } else {
            handshaker.handshake(context.channel(), request);
        }
    }

    /**
     * 服务端给客户端的响应
     *
     * @param context  通道上下文对象
     * @param request  http请求对象
     * @param response http响应对象
     */
    private void sendHttpResponse(ChannelHandlerContext context, FullHttpRequest request,
                                  DefaultFullHttpResponse response) {

        // 响应的状态码不等于200, 请求失败了, 将状态码放入一个缓冲中在放入response中
        if (response.getStatus().code() != HttpResponseStatus.OK.code()) {
            ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
        }

        // 服务端向客户端发送数据
        ChannelFuture channelFuture = context.channel().writeAndFlush(response);
        if (response.getStatus().code() != HttpResponseStatus.OK.code()) {
            channelFuture.addListener(ChannelFutureListener.CLOSE);
        }

    }

    /**
     * 处理服务端和客户端之间的webSocket通信
     *
     * @param context 通道上下文对象
     * @param frame   webSocket框架对象
     */
    private void handWebSocketFrame(ChannelHandlerContext context, WebSocketFrame frame) {
        // 判断是否是关闭webSocket的指令
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(context.channel(), (CloseWebSocketFrame) frame.retain());
        }

        // 判断是否是ping消息
        if (frame instanceof PingWebSocketFrame) {
            context.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }

        // 判断是否是二进制消息
        if (frame instanceof TextWebSocketFrame) {
            System.out.println("不支持二进制消息");
            new RuntimeException("[" + this.getClass().getName() + "] 不支持消息");
        }

        // 返回应答消息
        String request = ((TextWebSocketFrame) frame).text();
        System.out.println("服务端收到客户端的消息===========》" + request);
        TextWebSocketFrame textWebSocketFrame =
                new TextWebSocketFrame(new Date().toString() + ":" + context.channel().id() + "====>" + request);

        // 群发, 服务端向每一个连接上来的客户端都发送消息
        NettyConfig.group.writeAndFlush(textWebSocketFrame);
    }
}
